// SPDX-License-Identifier: MPL-2.0

use super::SyscallReturn;
use crate::{
    fs,
    fs::file_table::{get_file_fast, FileDesc},
    prelude::*,
    util::VmWriterArray,
};

pub fn sys_readv(
    fd: FileDesc,
    io_vec_ptr: Vaddr,
    io_vec_count: usize,
    ctx: &Context,
) -> Result<SyscallReturn> {
    let res = do_sys_readv(fd, io_vec_ptr, io_vec_count, ctx)?;
    Ok(SyscallReturn::Return(res as _))
}

pub fn sys_preadv(
    fd: FileDesc,
    io_vec_ptr: Vaddr,
    io_vec_count: usize,
    offset_low: u64,
    _offset_high: u64,
    ctx: &Context,
) -> Result<SyscallReturn> {
    // On 64-bit platforms, Linux assumes the `offset_low` contains the full
    // 64-bit offset.
    // Reference: <https://elixir.bootlin.com/linux/v6.16.9/source/fs/read_write.c#L1114-L1118>.
    let offset = offset_low.cast_signed();
    let res = do_sys_preadv(fd, io_vec_ptr, io_vec_count, offset, RWFFlag::empty(), ctx)?;
    Ok(SyscallReturn::Return(res as _))
}

pub fn sys_preadv2(
    fd: FileDesc,
    io_vec_ptr: Vaddr,
    io_vec_count: usize,
    offset_low: u64,
    _offset_high: u64,
    flags: u32,
    ctx: &Context,
) -> Result<SyscallReturn> {
    // On 64-bit platforms, Linux assumes the `offset_low` contains the full
    // 64-bit offset.
    // Reference: <https://elixir.bootlin.com/linux/v6.16.9/source/fs/read_write.c#L1114-L1118>.
    let offset = offset_low.cast_signed();
    let flags = match RWFFlag::from_bits(flags) {
        Some(flags) => flags,
        None => return_errno_with_message!(Errno::EINVAL, "invalid flags"),
    };
    let res = if offset == -1 {
        do_sys_readv(fd, io_vec_ptr, io_vec_count, ctx)?
    } else {
        do_sys_preadv(fd, io_vec_ptr, io_vec_count, offset, flags, ctx)?
    };
    Ok(SyscallReturn::Return(res as _))
}

fn do_sys_preadv(
    fd: FileDesc,
    io_vec_ptr: Vaddr,
    io_vec_count: usize,
    offset: i64,
    _flags: RWFFlag,
    ctx: &Context,
) -> Result<usize> {
    debug!(
        "preadv: fd = {}, io_vec_ptr = 0x{:x}, io_vec_counter = 0x{:x}, offset = 0x{:x}",
        fd, io_vec_ptr, io_vec_count, offset
    );

    if offset < 0 {
        return_errno_with_message!(Errno::EINVAL, "offset cannot be negative");
    }

    let mut file_table = ctx.thread_local.borrow_file_table_mut();
    let file = get_file_fast!(&mut file_table, fd);

    if io_vec_count == 0 {
        return Ok(0);
    }

    let mut total_len: usize = 0;
    let mut cur_offset = offset as usize;

    let user_space = ctx.user_space();
    let mut writer_array = VmWriterArray::from_user_io_vecs(&user_space, io_vec_ptr, io_vec_count)?;
    for writer in writer_array.writers_mut() {
        debug_assert!(writer.has_avail());

        // TODO: According to the man page
        // at <https://man7.org/linux/man-pages/man2/readv.2.html>,
        // readv must be atomic,
        // but the current implementation does not ensure atomicity.
        // A suitable fix would be to add a `readv` method for the `FileLike` trait,
        // allowing each subsystem to implement atomicity.
        match file.read_at(cur_offset, writer) {
            Ok(read_len) => {
                total_len += read_len;
                cur_offset += read_len;
            }
            Err(_) if total_len > 0 => break,
            Err(err) => return Err(err),
        }
        if writer.has_avail() {
            // End of file reached or no more data to read
            break;
        }
    }

    if total_len > 0 {
        fs::notify::on_access(&file);
    }

    Ok(total_len)
}

fn do_sys_readv(
    fd: FileDesc,
    io_vec_ptr: Vaddr,
    io_vec_count: usize,
    ctx: &Context,
) -> Result<usize> {
    debug!(
        "fd = {}, io_vec_ptr = 0x{:x}, io_vec_counter = 0x{:x}",
        fd, io_vec_ptr, io_vec_count
    );

    let mut file_table = ctx.thread_local.borrow_file_table_mut();
    let file = get_file_fast!(&mut file_table, fd);

    if io_vec_count == 0 {
        return Ok(0);
    }

    let mut total_len = 0;

    let user_space = ctx.user_space();
    let mut writer_array = VmWriterArray::from_user_io_vecs(&user_space, io_vec_ptr, io_vec_count)?;
    for writer in writer_array.writers_mut() {
        debug_assert!(writer.has_avail());

        // TODO: According to the man page
        // at <https://man7.org/linux/man-pages/man2/readv.2.html>,
        // readv must be atomic,
        // but the current implementation does not ensure atomicity.
        // A suitable fix would be to add a `readv` method for the `FileLike` trait,
        // allowing each subsystem to implement atomicity.
        match file.read(writer) {
            Ok(read_len) => total_len += read_len,
            Err(_) if total_len > 0 => break,
            Err(err) => return Err(err),
        }
        if writer.has_avail() {
            // End of file reached or no more data to read
            break;
        }
    }

    if total_len > 0 {
        fs::notify::on_access(&file);
    }

    Ok(total_len)
}

bitflags! {
    struct RWFFlag: u32 {
        const RWF_DSYNC = 0x00000001;
        const RWF_HIPRI = 0x00000002;
        const RWF_SYNC = 0x00000004;
        const RWF_NOWAIT = 0x00000008;
    }
}
